local internalVersion = 1

XPerl_GrimReaper_Config = {
	dockToTooltip = true,
	scale	= 0.6,
	keep = 15,
	bars = true,
	estimate = true,
	barsLeft = true,
	enabled = true,
	dockPoint = 1,
}

local new, del
do
	local list = setmetatable({},{__mode='k'})
	function new(...)
		local t = next(list)
		if t then
			list[t] = nil
			for i = 1, select('#', ...) do
				t[i] = select(i, ...)
			end
			return t
		else
			return {...}
		end
	end
	function del(t)
		if (t) then
			for k in pairs(t) do
				t[k] = nil
			end
			t[''] = true
			t[''] = nil
			list[t] = true
		end
	end
end

local conf = XPerl_GrimReaper_Config

local lastTime = 0
local millisecs = 0

local gr = CreateFrame("Frame", "XPerl_GrimReaper")
gr:SetPoint("CENTER", 0, 0)
gr.list = setmetatable({},{
	__index = function(self, index)
		if (index) then
			local find = strlower(index)
			for k,v in pairs(self) do
				if (strlower(k) == find) then
					return v
				end
			end
		end
	end
})
gr.frameWidth = 190
--gr.timerAccuracy = 30		-- How many iterations needed to create accuracy timer, more is better

if not UIDropDownMenu_CreateInfo then
	local UIDropDownMenu_ButtonInfo
	function UIDropDownMenu_CreateInfo()
		-- Reuse the same table to prevent memory churn
		local info = UIDropDownMenu_ButtonInfo
		for k,v in pairs(info) do
			info[k] = nil
		end
		return UIDropDownMenu_ButtonInfo
	end
end

gr:SetWidth(1)
gr:SetHeight(1)

local schools = {DAMAGE_SCHOOL2, DAMAGE_SCHOOL3, DAMAGE_SCHOOL4, DAMAGE_SCHOOL5, DAMAGE_SCHOOL6, DAMAGE_SCHOOL7}
local schoolColour = {{1, 1, 0.5}, {1, 0, 0}, {0, 1, 0}, {0.3, 0.3, 1}, {0.5, 0, 0.5}, {0.5, 0.5, 0.5}}
schools[0] = SPELL_SCHOOL0_CAP
schoolColour[0] = {1, 0.5, 0.5}
local healColour = {1, 1, 1}
local baseColour = {1, 1, 1}
local specialColour = {0.5, 1, 1}

local recent = setmetatable({}, {__mode = "k"})

if (not XPerlColourTable) then
	XPerlColourTable = setmetatable({},{
		__mode = 'k',
		__index = function(self, class)
			local c = RAID_CLASS_COLORS[strupper(class)] or {r = 0, g = 0, b = 0}
			self[class] = format("|c00%02X%02X%02X", 255 * c.r, 255 * c.g, 255 * c.b)
			return self[class]
		end
	})
end

-- gr:OnEvent(event, a, b, c, d)
function gr:OnEvent(event, a, b, c, d, e)
	self[event](self, a, b, c, d, e)
end

-- The element contents:
-- 1 Damage Type
-- 2 Modifier
-- 3 Amount
-- 4 Magic School
-- 5 time()
-- 6 health (modified later from UNIT_HEALTH)
-- 7 milliseconds from last frame update
-- 8 Real UNIT_HEALTH received at this point
-- 9 Real UnitHealthMax

-- AddCombat
function gr:AddCombat(unit, dmgType, modifier, amount, school)
	if (dmgType == "WOUND" and amount == 0) then
		return
	end

	local name = UnitName(unit)

	local list = self.list[name]
	if (not list) then
		list = {}
		self.list[name] = list
	end

	-- Note that UnitHealth is not updated immediately at this point, so we setup the 'recent' table to look for UNIT_HEALTH
	-- event that comes for this unit shortly when we can reliably measure the unit's health

	local health
	if (UnitIsDeadOrGhost(unit)) then
		health = "DEAD"
	else
		health = min(100, floor(100 * UnitHealth(unit) / UnitHealthMax(unit)))
	end

	local n = new(dmgType, modifier, amount, school, time(), health, floor(millisecs * 1000))
	tinsert(list, n)
	recent[unit] = n
	self:RegisterEvent("UNIT_HEALTH")

	while (#list > conf.keep) do
		local o = list[1]
		del(o)
		tremove(list, 1)
	end

	if (self.lastUnit and UnitIsUnit(unit, self.lastUnit)) then
		self:Tip()		-- Refresh if shown
	end
end

-- UNIT_COMBAT
-- We assign UNIT_COMBAT for whichever group type we're in, UNIT_COMBAT is called a LOT so I wanted this as fast as possible

function gr:UNIT_COMBAT_raid(unit, dmgType, modifier, amount, school)
	if (strfind(unit, "^raid%d+")) then
		self:AddCombat(unit, dmgType, modifier, amount, school)
	end
end

function gr:UNIT_COMBAT_party(unit, dmgType, modifier, amount, school)
	if (strfind(unit, "^party%d") or unit == "player") then
		self:AddCombat(unit, dmgType, modifier, amount, school)
	end
end

-- function gr:UNIT_COMBAT(unit, dmgType, modifier, amount, school)
-- 	local ok
-- 	if (GetNumRaidMembers() > 0) then
-- 		if (strfind(unit, "^raid%d+")) then
-- 			ok = true
-- 		end
-- 	else
-- 		if (strfind(unit, "^party%d") or unit == "player") then
-- 			ok = true
-- 		end
-- 	end
--
-- 	if (ok) then
-- 		self:AddCombat(unit, dmgType, modifier, amount, school)
-- 	end
-- end

-- gr:UNIT_HEALTH()
function gr:UNIT_HEALTH(unit)
	local r = recent[unit]
	if (r) then
		if (UnitIsDeadOrGhost(unit)) then
			r[6] = "DEAD"
		else
			r[6] = min(100, floor(100 * UnitHealth(unit) / UnitHealthMax(unit)))
		end
		r[8] = UnitHealth(unit)
		r[9] = UnitHealthMax(unit)
		recent[unit] = nil

		if (not next(recent)) then
			self:UnregisterEvent("UNIT_HEALTH")
		end

		if (unit == self.lastUnit) then
			self:Tip()		-- Refresh if shown
		end
	end
end

-- RAID_ROSTER_UPDATE
function gr:RAID_ROSTER_UPDATE()
	self:Validate()
end

-- PARTY_MEMBERS_CHANGED
function gr:PARTY_MEMBERS_CHANGED()
	if (GetNumRaidMembers() == 0) then
		-- Don't do this if in a raid, no need
		self:Validate()
	end
end

-- Validate
function gr:Validate()

	-- Clear out members of list that have left raid
	local current = new()
	if (GetNumRaidMembers() > 0) then
		for i = 1,GetNumRaidMembers() do
			local name = GetRaidRosterInfo(i)
			if (name) then
				current[name] = true
			end
		end

		self.UNIT_COMBAT = self.UNIT_COMBAT_raid
	else
		current[UnitName("player")] = true
		for i = 1,GetNumPartyMembers() do
			local name = UnitName("party"..i)
			if (name) then
				current[name] = true
			end
		end

		self.UNIT_COMBAT = self.UNIT_COMBAT_party
	end

	for k,v in pairs(self.list) do
		if (not current[k]) then
			self.list[k] = nil
		end
	end

	del(current)
end

-- gr:Timer(elapsed)
-- This gives us a time accuracy of 100th seconds. The actual time is not important, only the time 'between' events, so this will do
function gr:Timer(elapsed)
	local t = time()
	if (t > lastTime) then
		lastTime = t
		millisecs = 0
	else
		millisecs = millisecs + elapsed
		if (millisecs >= 1) then
			millisecs = 0.999
		end
	end
end

-- PLAYER_ENTERING_WORLD
function gr:PLAYER_ENTERING_WORLD()

	if (GetNumRaidMembers() > 0) then
		self.UNIT_COMBAT = self.UNIT_COMBAT_raid
	else
		self.UNIT_COMBAT = self.UNIT_COMBAT_party
	end

	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
	self.PLAYER_ENTERING_WORLD = nil
end

-- VARIABLES_LOADED
function gr:VARIABLES_LOADED()
	conf = XPerl_GrimReaper_Config

	conf.locked = nil
	if (conf.enabled == nil) then
		conf.enabled = true
	end
	if (conf.bars == nil) then
		conf.bars = true
	end
	if (conf.barsLeft == nil) then
		conf.barsLeft = true
	end

	if ((conf.channel == "WHISPER" or conf.channel == "CHANNEL") and not conf.channelIndex) then
		conf.channel = "RAID"
	end

	conf.scale	= conf.scale	or 0.6
	conf.keep	= conf.keep	or 15

	conf.version = conf.version or 0
	if (conf.version < internalVersion) then
		if (conf.version < 1) then
			conf.estimate = true
		end
	end

	hooksecurefunc(GameTooltip, "SetUnit", self.TipHook_SetUnit)

	self:RestorePosition()

	self:Enable()

	self.VARIABLES_LOADED = nil
end

-- gr:Enable()
function gr:Enable()
	if (conf.enabled) then
		self:SetScript("OnUpdate", self.Timer)

		self:RegisterEvent("RAID_ROSTER_UPDATE")
		self:RegisterEvent("PARTY_MEMBERS_CHANGED")
		self:RegisterEvent("UNIT_COMBAT")
	else
		self:SetScript("OnUpdate", nil)

		self:UnregisterEvent("RAID_ROSTER_UPDATE")
		self:UnregisterEvent("PARTY_MEMBERS_CHANGED")
		self:UnregisterEvent("UNIT_COMBAT")
		self:UnregisterEvent("UNIT_HEALTH")
	end
	self:Tip()
end

-- TipHook_OnShow
function gr:TipHook_SetUnit(unit)
	XPerl_GrimReaper:Tip(unit, self)
end

-- gr:FixLastUnit()
function gr:FixUnit(unit)
	if (GetNumRaidMembers() > 0) then
		if (not strfind(unit, "^raid")) then
			for i = 1,GetNumRaidMembers() do
				if (UnitIsUnit("raid"..i, unit)) then
					return "raid"..i
				end
			end
		end

	else
		if (UnitIsUnit("player", unit)) then
			return "player"
		end

		if (GetNumPartyMembers() > 0) then
			if (not strfind(unit, "^party")) then
				for i = 1,GetNumRaidMembers() do
					if (UnitIsUnit("party"..i, unit)) then
						return "party"..i
					end
				end
			end
		end
	end

	return unit
end

-- CreateAttachment
function gr:CreateAttachment()

	self.attachment = CreateFrame("Frame", "XPerl_GrimReaper_Attachment", UIParent)
	local att = self.attachment
	att:SetScale(conf.scale)

	att:SetWidth(self.frameWidth)
	att:SetHeight(100)
	att:SetClampedToScreen(true)

	local bgDef = {bgFile = "Interface\\Addons\\XPerl\\Images\\XPerl_FrameBack",
			edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
			tile = true, tileSize = 32, edgeSize = 24,
			insets = { left = 6, right = 6, top = 6, bottom = 6 }
		}
	att:SetBackdrop(bgDef)
	att:SetBackdropColor(0, 0, 0, 0.8)
	att:SetBackdropBorderColor(1, 1, 1, 1)

	att.title = att:CreateFontString(nil, "BORDER", "GameFontNormal")
	att.title:SetText(XPerl_ShortProductName.." "..XPERL_REAPER_TITLE)
	att.title:SetPoint("BOTTOM", att, "TOP", 0, 0)
	att.title:SetHeight(15)
	att.title:SetWidth(self.frameWidth)
	att.title:SetTextColor(1, 1, 1)

	att.frames = {}

	GameTooltip:HookScript("OnHide",
		function(self)
			if (conf.dockToTooltip) then
				XPerl_GrimReaper_Attachment:Hide()
				XPerl_GrimReaper.lastUnit = nil
			end
		end)

	att:SetScript("OnDragStart",
		function(self)
			if (not conf.dockToTooltip) then
				XPerl_GrimReaper:StartMoving()
			end
		end)

	att:SetScript("OnDragStop",
		function(self)
			XPerl_GrimReaper:StopMovingOrSizing()
			XPerl_GrimReaper:SavePosition()
			XPerl_GrimReaper:Tip()
		end)

	att:SetScript("OnMouseUp",
		function(self, button)
			if (button == "RightButton") then
				XPerl_GrimReaper:ShowMenu()
			end
		end)

	att:SetScript("OnMouseWheel",
		function(self, amount)
			if (IsAltKeyDown()) then
				conf.scale = conf.scale + (0.025 * amount)
				if (conf.scale < 0.2) then
					conf.scale = 0.2
				elseif (conf.scale > 2) then
					conf.scale = 2
				end
				self:SetScale(conf.scale)
			end
		end)

	self.CreateAttachment = nil

	return att
end

-- SetAttachPoint
function gr:SetAttachPoint()
	local att = self.attachment
	if (not att) then
		att = self:CreateAttachment()
	end

	self.lastTip = self.lastTip or GameTooltip

	att:ClearAllPoints()
	if (conf.dockToTooltip) then
		att:SetParent(self.lastTip)
		att:SetPoint("BOTTOMRIGHT", self.lastTip, "BOTTOMLEFT", 0, 0)
		self:SetMovable(false)
		att:EnableMouseWheel(false)
		att:EnableMouse(false)

		att:ClearAllPoints()
		if (not conf.dockPoint) then
			conf.dockPoint = 1
		end
		if (conf.dockPoint == 1) then
			att:SetPoint("BOTTOMRIGHT", self.lastTip, "BOTTOMLEFT", 0, 0)
		elseif (conf.dockPoint == 2) then
			att:SetPoint("BOTTOMLEFT", self.lastTip, "BOTTOMRIGHT", 0, 0)
		end
	else
		att:SetParent(UIParent)
		att:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
		self:SetMovable(true)
		att:EnableMouse(true)
		att:EnableMouseWheel(true)
		att:RegisterForDrag("LeftButton")
	end
end

-- gr:Estimate()
function gr:Estimate(unit, list, k)
	if (list[k][8]) then
		return list[k][6]
	end
	if (list[k][6] == "DEAD") then
		return "DEAD"
	end

	local health, hpMax, startIndex

	for i = k,1,-1 do
		if (list[i][8]) then
--ChatFrame2:AddMessage("Estimating line "..k.." from line "..i)
			startIndex = i + 1
			health = list[i][8]
			hpMax = list[i][9]
			break
		end
	end

	if (health) then
		if (health == "DEAD") then
			health = 0
		end
		for i = startIndex,k do
			if (list[k][1] == "HEAL") then
				health = min(hpMax, max(0, health + list[i][6]))
			elseif (list[k][1] == "WOUND") then
				health = min(hpMax, max(0, health - list[i][6]))
			end
		end
		return min(100, floor(100 * health / hpMax)), true
	end

	return list[k][6]
end

-- gr:Title
function gr:Title(unit)
	if (not unit) then
		unit = self.lastUnit
	end
	if (unit) then
		local unitName = UnitName(unit)
		local str = XPERL_REAPER_TITLE
		if (unitName) then
			str = format("%s - %s%s%s", str, XPerlColourTable[select(2, UnitClass(unit))], unitName, (conf.locked and " |c00FF8080(L)") or "")
		else
			str = XPERL_REAPER_TITLE
		end
		self.attachment.title:SetText(str)
	end
end

-- Tip
function gr:Tip(unit, tooltip)

	local att = self.attachment
	if (not conf.enabled or conf.hide) then
		if (att) then
			att:Hide()
		end
		return
	end

	if (not unit) then
		unit = self.lastUnit
		tooltip = self.lastTip
	else
		if (conf.locked) then
			return
		end
	end
	self.lastTip = tooltip or GameTooltip

	if (not unit or not UnitExists(unit) or (not UnitInRaid(unit) and not UnitInParty(unit) and unit ~= "player")) then
		return
	end

	if (not att) then
		att = self:CreateAttachment()
	end
	self:SetAttachPoint()

	local unitName = UnitName(unit)
	local list = self.list[unitName]

	if ((not list or #list == 0) and (self.lastUnit and self.list[UnitName(self.lastUnit)])) then
		unit = self.lastUnit
		unitName = UnitName(unit)
		list = self.list[unitName]
	end

	unit = self:FixUnit(unit)

	if (list) then
		self.lastUnit = unit
		self:Title()

		if (self.emptyText) then
			self.emptyText:Hide()
		end

		local lastTime = list[#list][5]

		local barTex
		if (XPerl_GetBarTexture) then
			barTex = XPerl_GetBarTexture()
		else
			barTex = "Interface\\TargetingFrame\\UI-StatusBar"
		end

		local i = 1
		for k = #list,1,-1 do
			local v = list[k]
			local frame = att.frames[i]
			if (not frame) then
				frame = CreateFrame("Frame", "XPerl_GrimReaper_Attachment"..k, att)
				att.frames[i] = frame

				if (i == 1) then
					frame:SetPoint("BOTTOMRIGHT", att, "BOTTOMRIGHT", -6, 10)
				else
					frame:SetPoint("BOTTOMRIGHT", att.frames[i - 1], "TOPRIGHT", 0, 0)
				end

				frame:SetWidth(self.frameWidth - 10)
				frame:SetHeight(15)

				frame.text = frame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
				frame.text:SetJustifyH("RIGHT")
				frame.text:SetAllPoints(frame)

				frame.time = frame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
				frame.time:SetJustifyH("LEFT")
				frame.time:SetAllPoints(frame)
				frame.time:SetTextColor(1, 1, 0.5)
			end

			if (conf.bars) then
				local health, estimated
				if (conf.estimate) then
					health, estimated = self:Estimate(unit, list, k)
				else
					health = v[6]
				end

				if (not frame.texture) then
					frame.texture = frame:CreateTexture(nil, "BORDER")
					frame.texture:SetWidth(1)
					frame.texture:SetHeight(15)

					frame.healthText = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
					frame.healthText:SetWidth(90)
					frame.healthText:SetTextColor(1, 1, 1)
				end

				frame.texture:SetTexture(barTex)

				frame.texture:ClearAllPoints()
				frame.healthText:ClearAllPoints()
				if (conf.barsLeft) then
					frame.texture:SetPoint("RIGHT", frame.text, "LEFT", -4, 0)
					frame.healthText:SetJustifyH("RIGHT")
					frame.healthText:SetPoint("RIGHT", frame.text, "LEFT", -7, 1)
				else
					frame.texture:SetPoint("LEFT", frame.text, "RIGHT", 4, 0)
					frame.healthText:SetJustifyH("LEFT")
					frame.healthText:SetPoint("LEFT", frame.text, "RIGHT", 7, 1)
				end
				frame.texture:Show()
				frame.healthText:Show()

				--if (v[6] == "DEAD") then
				if (health == "DEAD") then
					frame.healthText:SetText(DEAD)
					frame.texture:SetWidth(1)
					frame.texture:SetVertexColor(1, 0, 0)
				else
					--frame.healthText:SetText(format("%d"..PERCENT_SYMBOL, v[6]))
					--frame.texture:SetWidth(max(1, v[6]))
					if (estimated) then
						frame.healthText:SetFormattedText("* %d"..PERCENT_SYMBOL, health)
					else
						frame.healthText:SetFormattedText("%d"..PERCENT_SYMBOL, health)
					end
					frame.texture:SetWidth(max(1, health))

					local r, g, b
					--local percentage = v[6] / 100
					local percentage = health / 100
					if (percentage < 0.5) then
						r = 1
						g = 2*percentage
						b = 0
					else
						g = 1
						r = 2*(1 - percentage)
						b = 0
					end

					if (r >= 0 and g >= 0 and b >= 0 and r <= 1 and g <= 1 and b <= 1) then
						frame.texture:SetVertexColor(r, g, b)
					end
				end
			else
				if (frame.texture) then
					frame.texture:Hide()
					frame.healthText:Hide()
				end
			end

			local str
			local color = schoolColour[v[4]] or basicColour

			if (XPERL_REPEAR_SPECIALS[v[1]]) then
				color = specialColour
				str = XPERL_REPEAR_SPECIALS[v[1]]

			elseif (v[2] and XPERL_REPEAR_SPECIALS[v[2]]) then
				color = specialColour
				str = XPERL_REPEAR_SPECIALS[v[2]]

			elseif (v[1] == "HEAL") then
				color = healColour
				str = "+"..tostring(v[3])
			else
				str = "-"..tostring(v[3])
			end

			if (v[2] == "CRUSHING") then
				str = XPERL_REAPER_CRUSHING.." "..str
			end

			frame.text:SetText(str)
			frame.text:SetTextColor(unpack(color))		--color[1], color[2], color[3])

			if (v[2] == "CRITICAL" or v[2] == "CRUSHING") then
				frame.text:SetFontObject("GameFontNormalLarge")
			else
				frame.text:SetFontObject("GameFontNormal")
			end

			frame.time:SetText(date(XPERL_REAPER_TIMEFORMAT, v[5]).."|c00C0FFC0"..self:MakeFractionalTime(v[7]))

			frame:Show()
			i = i + 1
		end
		for i = #list + 1,conf.keep do
			local frame = att.frames[i]
			if (frame) then
				frame:Hide()
			end
		end

		att:SetHeight(15 * #list + 17)
		self:SetHeight(15 * #list + 17)
		att:Show()
	else
		if (conf.dockToTooltip) then
			att:Hide()
		else
			-- Show blank reaper

			self.lastUnit = unit
			self:Title()

			for k,v in pairs(att.frames) do
				v:Hide()
			end

			if (not self.emptyText) then
				self.emptyText = att:CreateFontString(nil, "ARTWORK", "GameFontNormal")
				self.emptyText:SetAllPoints(att)
				self.emptyText:SetTextColor(0.5, 0.5, 0.5)
				self.emptyText:SetText(XPERL_REAPER_NOINFO)
			end
			self.emptyText:Show()

			att:SetHeight(42)
			self:SetHeight(42)
			att:Show()
		end
	end
end

-- SavePosition
function gr:SavePosition()
	if (self.attachment) then
		conf.SavedPosition = {top = self:GetTop(), left = self:GetLeft()}
	end
end

-- RestorePosition
function gr:RestorePosition()
	local pos = conf.SavedPosition
	if (pos) then
		if (pos.left or pos.right) then
			self:ClearAllPoints()
			self:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", pos.left, pos.top)
		end
	end
end

-- Output
function gr:Output(...)
	local channel = conf.channel
	if (channel == "RAID" and GetNumRaidMembers() == 0) then
		channel = "PARTY"
	end
	if (channel == "PARTY" and GetNumPartyMembers() == 0) then
		return
	end
	if (channel == "CHANNEL" and GetChannelName(conf.channelIndex) == 0) then
		return
	end

	SendChatMessage(format(...), channel, nil, conf.channelIndex)
end

-- self:Msg(str)
function gr:Msg(...)
	DEFAULT_CHAT_FRAME:AddMessage("|c00FFFF80"..format(...))
end

-- MakeFractionalTime
function XPerl_GrimReaper:MakeFractionalTime(getTime)
	if (getTime) then
		return format(".%03d", getTime)
	end
	return ""
end

-- SetChannel
function gr:SetChannel(chan)
	local oldChannel = conf.channel
	if (chan) then
		conf.channel = strupper(chan)

		if (tonumber(conf.channel) and tonumber(conf.channel) > 0) then
			if (GetChannelName(conf.channelIndex) > 0) then
				conf.channelIndex = tonumber(conf.channel)
				conf.channel = "CHANNEL"
			else
				conf.channel = oldChannel
				self:Msg(XPERL_REAPER_CHANNEL_INVALID)
				return
			end

		elseif (not (conf.channel == "RAID" or conf.channel == "PARTY" or conf.channel == "GUILD" or conf.channel == "SAY" or conf.channel == "BATTLEGROUND" or conf.channel == "YELL" or conf.channel == "OFFICER")) then
			local c = conf.channel
			conf.channelIndex = conf.channel
			conf.channel = "WHISPER"
			for i = 1,10 do
				local used, name = GetChannelName(i)
				if (name) then
					if (strlower(name) == strlower(c)) then
						conf.channelIndex = name
						conf.channel = "CHANNEL"
						break
					end
				end
			end
		end
	end

	if (not conf.channel) then
		conf.channel = "RAID"
	end

	self:Msg(XPERL_REAPER_CHANNEL, self:GetChannelDisplay())
end

-- GetChannelDisplay
function gr:GetChannelDisplay()
	local c, ret
	if (conf.channel == "WHISPER") then
		local color
		for unit, name, class in XPerl_NextMember do
			if (strlower(name) == strlower(conf.channelIndex)) then
				color = XPerlColourTable[class]
				break
			end
		end

		ret = (getglobal(conf.channel) or conf.channel).." "..(color or "")..conf.channelIndex.."|r"

	elseif (conf.channel == "CHANNEL") then
		local id, name = GetChannelName(conf.channelIndex)
		if (name) then
			local t = ChatTypeInfo["CHANNEL"..conf.channelIndex]
			if (t) then
				c = {r = t.r, g = t.g, b = t.b}
			end
			ret = name
		end
	else
		ret = getglobal("CHAT_MSG_"..(conf.channel or "RAID")) or conf.channel or "RAID"
	end

	if (not ret) then
		ret = "RAID"
	end

	if (not c) then
		if (ChatTypeInfo[conf.channel]) then
			local t = ChatTypeInfo[conf.channel]
			if (t) then
				c = {r = t.r, g = t.g, b = t.b}
			end
		end
	end

	if (c) then
		ret = format("|c00%02X%02X%02X%s|r", c.r * 255, c.g * 255, c.b * 255, ret)
	end

	return ret
end

-- Report
function gr:Report(args)

	local unitName = args and args[1]
	local channel = "RAID"

	if (args and args[2]) then
		self:SetChannel(args[2])
	end

	if (not unitName and self.lastUnit) then
		unitName = UnitName(self.lastUnit)
	end
	if (not unitName) then
		return
	end

	local list = self.list[unitName]

	if (list) then
		local lastFullTime = list[#list][5] + list[#list][7] / 1000

		local last100 = 0
		if (conf.scan) then
			-- Scan for last point where the player was at 100% hp. only report from there
			for k,v in ipairs(list) do
				if (v[6] == 100) then
					if (k < (#list - 1)) then
						last100 = k
					end
				end
			end

			if (last100 > #list) then
				self:Msg(XPERL_REPEAR_REPORTNOTHING, unitName)
				return
			end
		end

		self:Output(XPERL_REPEAR_REPORTTITLE, unitName)

		for k = last100 + 1, #list do
			local v = list[k]
			local str

			local thisFullTime = v[5] + v[7] / 1000

			if (k == #list) then
				str = date(XPERL_REAPER_TIMEFORMAT, v[5])..self:MakeFractionalTime(v[7]).."> "
			else
			        str = strrep(" ",11)..format("%.3f> ", thisFullTime - lastFullTime)
			end

			if (XPERL_REPEAR_SPECIALS[v[1]]) then
				str = str..XPERL_REPEAR_SPECIALS[v[1]]

			elseif (XPERL_REPEAR_SPECIALS[v[2]]) then
				str = str..XPERL_REPEAR_SPECIALS[v[2]]

			elseif (v[1] == "HEAL") then
				str = str..XPERL_REPEAR_REPORTHEAL.." +"..tostring(v[3])

			else
				str = str.."-"..tostring(v[3])
			end

			if (v[2] ~= "") then
				if (v[2] == "CRUSHING") then
					str = str.." ("..XPERL_REPEAR_REPORTCRUSH..")"
				elseif (v[2] == "CRITICAL") then
					str = str.." ("..XPERL_REPEAR_REPORTCRIT..")"
				end
			end

			if (v[4] ~= 0 and schools[v[4]]) then
				str = str.." ["..schools[v[4]].."]"
			end

			if (v[6] == "DEAD") then
				local dead = "_____"..DEAD.."_____"
				strsub(dead, strlen(dead) / 2, 10)
				dead = strsub(dead, strlen(dead) / 2 - 4, strlen(dead) / 2 + 5)
				str = dead.." "..str
			else
				local c = v[6] / 10
				str = strrep("#", c)..strrep("_", 10 - c).." "..str
			end

			self:Output(str)
		end
	else
		self:Msg(XPERL_REPEAR_REPORT_NF, unitName)
	end
end

-- ShowMenu
local channelList
function gr:ShowMenu()

	if (not self.menu) then
		self.menu = CreateFrame("Frame", "XPerl_GrimReaper_Menu")
		self.menu.displayMode = "MENU"
		UIDropDownMenu_Initialize(self.menu,
			function(level)
				XPerl_GrimReaper:Menu_Initialize(level)
			end
		)
	end

	ToggleDropDownMenu(1, nil, XPerl_GrimReaper_Menu, "cursor")
end

-- XPerl_GetChannelList
if (not XPerl_GetChannelList) then
local function GetChatColour(name)
	local info = ChatTypeInfo[name]
	local clr = {r = 0.5, g = 0.5, b = 0.5}
	if (info) then
		clr.r = (info.r or 0.5)
		clr.g = (info.g or 0.5)
		clr.b = (info.b or 0.5)
	end
	return clr
end
function XPerl_GetChannelList()
	local cList = {}
	local l = {"RAID", "OFFICER", "GUILD", "PARTY", "SAY"}
	for k,v in pairs(l) do
		tinsert(cList, {display = getglobal("CHAT_MSG_"..v), channel = v, colour = GetChatColour(v)})
	end

	for i = 1,10 do
		local c, name = GetChannelName(i)
		if (name and c ~= 0) then
			tinsert(cList, {display = name, channel = "CHANNEL", index = c, colour = GetChatColour("CHANNEL"..c)})
		end
	end

	return cList
end
end

-- ShowMenu
local function ChannelOnClick(val, a)
	conf.channel = this.value[1]
	conf.channelIndex = this.value[2]
	gr:SetChannel()			-- Will just display the channel
end

if (not XPerl_NextMember) then
function XPerl_NextMember(_, last)
	if (last) then
		local raidCount = GetNumRaidMembers()
		if (raidCount > 0) then
			local i = tonumber(strmatch(last, "^raid(%d+)"))
			if (i and i < raidCount) then
				i = i + 1
				local unitName, rank, group, level, _, unitClass, zone, online, dead = GetRaidRosterInfo(i)
				return "raid"..i, unitName, unitClass, group, zone, online, dead
			end
		else
			local partyCount = GetNumPartyMembers()
			if (partyCount > 0) then
				local id
				if (last == "player") then
					id = "party1"
				else
					local i = tonumber(strmatch(last, "^party(%d+)"))
					if (i and i < partyCount) then
						i = i + 1
						id = "party"..i
					end
				end

				if (id) then
					return id, UnitName(id), select(2, UnitClass(id)), 1, "", UnitIsConnected(id), UnitIsDeadOrGhost(id)
				end
			end
		end
	else
		if (GetNumRaidMembers() > 0) then
			local unitName, rank, group, level, _, unitClass, zone, online, dead = GetRaidRosterInfo(1)
			return "raid1", unitName, unitClass, group, zone, online, dead
		else
			return "player", UnitName("player"), select(2, UnitClass("player")), 1, GetRealZoneText(), 1, UnitIsDeadOrGhost("player")
		end
	end
end
end

function gr:Menu_Initialize(level)

	local info

	if (level == 2) then
		if (UIDROPDOWNMENU_MENU_VALUE == "channel") then
			channelList = XPerl_GetChannelList()

			info = UIDropDownMenu_CreateInfo()
			info.isTitle = 1
			info.text = CHANNELS
			UIDropDownMenu_AddButton(info, level)

			for i,entry in pairs(channelList) do
				info = UIDropDownMenu_CreateInfo()
				info.text = entry.display
				info.func = ChannelOnClick
				info.value = {entry.channel, entry.index}
				info.textR = entry.colour.r
				info.textG = entry.colour.g
				info.textB = entry.colour.b

				info.checked = nil
				if (entry.channel == conf.channel) then
					if (conf.channel ~= "CHANNEL" or entry.index == conf.channelIndex) then
						info.checked = 1
					end
				end

				UIDropDownMenu_AddButton(info, level)
			end

			info = UIDropDownMenu_CreateInfo()
			info.hasArrow = 1
			info.text = WHISPER
			info.func = nil
			info.value = "whisper"
			info.textR = ChatTypeInfo.WHISPER.r
			info.textG = ChatTypeInfo.WHISPER.g
			info.textB = ChatTypeInfo.WHISPER.b
			info.checked = (conf.channel == "WHISPER")
			UIDropDownMenu_AddButton(info, level)
			return
		end
	elseif (level == 3) then
		if (UIDROPDOWNMENU_MENU_VALUE == "whisper") then

			local list = {}
			for unit, name, class in XPerl_NextMember do
				tinsert(list, {name, class})
			end
			sort(list, function(a, b) return a[1] < b[1] end)

			for k,v in pairs(list) do
				info = UIDropDownMenu_CreateInfo()
				info.text = v[1]
				info.func = ChannelOnClick
				info.value = {"WHISPER", v[1]}
				info.textR = RAID_CLASS_COLORS[v[2]].r
				info.textG = RAID_CLASS_COLORS[v[2]].g
				info.textB = RAID_CLASS_COLORS[v[2]].b

				info.checked = nil
				if (conf.channel == "WHISPER" and conf.channelIndex) then
					if (strlower(v[1]) == strlower(conf.channelIndex)) then
						info.checked = 1
					end
				end

				UIDropDownMenu_AddButton(info, level)
			end
			return
		end
	end

	info = UIDropDownMenu_CreateInfo()
	info.isTitle = 1
	info.text = XPERL_REAPER_TITLE
	UIDropDownMenu_AddButton(info)

	if (self.lastUnit) then
		info = UIDropDownMenu_CreateInfo()
		info.func = function()
				XPerl_GrimReaper:Report()
			end
		info.text = XPERL_REAPER_MENU_REPORT
		local bind = GetBindingKey("XPERL_GRIMREAPER_REPORT")
		if (bind) then
			info.text = info.text.." ("..bind..")"
		end
		info.disabled = not self.lastUnit or not self.list[UnitName(self.lastUnit)]
		UIDropDownMenu_AddButton(info)

		info = UIDropDownMenu_CreateInfo()
		info.func = function()
				if (self.lastUnit or conf.locked) then
					conf.locked = not conf.locked
					XPerl_GrimReaper:Tip()
				end
			end
		if (conf.locked and self.lastUnit and UnitName(self.lastUnit)) then
			info.text = format(XPERL_REAPER_MENU_LOCKED, UnitName(self.lastUnit))
		else
			info.text = XPERL_REAPER_MENU_LOCK
		end

		local bind = GetBindingKey("XPERL_GRIMREAPER_LOCK")
		if (bind) then
			info.text = info.text.." ("..bind..")"
		end
		info.checked = conf.locked
		UIDropDownMenu_AddButton(info)
	end

	info = UIDropDownMenu_CreateInfo()
	info.func = function()
			conf.scan = not conf.scan
		end
	info.checked = conf.scan
	info.text = XPERL_REAPER_MENU_SCANBACK
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.func = function()
			conf.bars = not conf.bars
			XPerl_GrimReaper:Tip()
		end
	info.checked = conf.bars
	info.text = XPERL_REAPER_MENU_BARS
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.func = function()
			conf.barsLeft = not conf.barsLeft
			XPerl_GrimReaper:Tip()
		end
	info.checked = conf.barsLeft
	info.text = XPERL_REAPER_MENU_BARSLEFT
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.func = function()
			conf.estimate = not conf.estimate
			XPerl_GrimReaper:Tip()
		end
	info.checked = conf.estimate
	info.text = XPERL_REAPER_MENU_ESTIMATE
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.func = function()
			conf.dockToTooltip = not conf.dockToTooltip
			XPerl_GrimReaper:Tip()
		end
	info.text = XPERL_REAPER_MENU_DOCK
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.func = function()
			conf.enabled = false
			self:Enable()
		end
	info.text = XPERL_REAPER_MENU_DISABLE
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.hasArrow = 1
	info.text = CHANNELS
	info.value = "channel"
	UIDropDownMenu_AddButton(info)

	info = UIDropDownMenu_CreateInfo()
	info.func = function()
			XPerl_GrimReaper.attachment:Hide()
			XPerl_GrimReaper.lastUnit = nil
			XPerl_GrimReaper.lastTip = nil
		end
	info.text = XPERL_REAPER_MENU_HIDE
	UIDropDownMenu_AddButton(info)
end

-- OnOff
local function OnOff(onoff)
	if (onoff) then
		return "("..XPERL_REAPER_ON_CLR..XPERL_REAPER_ON.."|r)"
	end
	return "("..XPERL_REAPER_OFF_CLR..XPERL_REAPER_OFF.."|r)"
end

-- Lock
function gr:Lock()
	if (self.lastUnit or conf.locked) then
		conf.locked = not conf.locked
		self:Msg(XPERL_REAPER_LOCKED, OnOff(conf.locked))
		self:Tip()
	end
end

-- SlashCmd
function gr:SlashCmd(msg)

	if (not msg or msg == "") then
		for i = 1,100 do
			local text = getglobal("XPERL_REAPER_HELP"..i)
			if (not text) then
				break
			end

			if (strfind(text, XPERL_REAPER_CMD_ENABLE)) then
				text = format(text, OnOff(conf.enabled))
			elseif (strfind(text, XPERL_REAPER_CMD_HIDE)) then
				text = format(text, OnOff(conf.hide))
			elseif (strfind(text, XPERL_REAPER_CMD_DOCK)) then
				text = format(text, OnOff(conf.dockToTooltip))
			elseif (strfind(text, XPERL_REAPER_CMD_BARS)) then
				text = format(text, OnOff(conf.bars))
			elseif (strfind(text, XPERL_REAPER_CMD_ESTIMATE)) then
				text = format(text, OnOff(conf.estimate))
			elseif (strfind(text, XPERL_REAPER_CMD_LOCK)) then
				if (conf.locked and UnitName(self.lastUnit)) then
					text = format(text, "|c00FFFF80"..UnitName(self.lastUnit).."|r")
				else
					text = format(text, OnOff(conf.locked))
				end
			elseif (strfind(text, XPERL_REAPER_CMD_KEEP)) then
				text = format(text, conf.keep)
			elseif (strfind(text, XPERL_REAPER_CMD_SCALE)) then
				text = format(text, conf.scale)
			elseif (strfind(text, XPERL_REAPER_CMD_CHANNEL)) then
				text = format(text, self:GetChannelDisplay())
			elseif (strfind(text, XPERL_REAPER_CMD_SCANBACK)) then
				text = format(text, OnOff(conf.scan))
			end

			if (strfind(text, XPERL_REAPER_CMD_LOCK)) then
				local bind = GetBindingKey("XPERL_GRIMREAPER_LOCK")
				if (bind) then
					text = text.." (|c00FF80FF"..bind.."|r)"
				end
			end

			DEFAULT_CHAT_FRAME:AddMessage(text)
		end
		return
	end

	local args = {}
	for token in msg:gmatch("([^%s]+)") do
		tinsert(args, strlower(token))
	end

	if (args[1] == XPERL_REAPER_CMD_KEEP) then
		local keep = tonumber(args[2])
		if (keep and keep >= 1 and keep <= 50) then
			conf.keep = keep
			self:Msg(XPERL_REAPER_KEEPING, keep)

			for k,v in pairs(self.list) do
				while (#v > conf.keep) do
					tremove(v, 1)
				end
			end
			self:Tip()
		end
		return

	elseif (args[1] == XPERL_REAPER_CMD_SCALE) then
		local scale = tonumber(args[2])
		if (scale and scale >= 0.2 and scale <= 2) then
			conf.scale = scale
			self:Msg(XPERL_REAPER_SCALED, conf.scale)
			self.attachment:SetScale(conf.scale)
			self:Tip()
		end
		return

	elseif (args[1] == XPERL_REAPER_CMD_DOCK) then
		conf.dockToTooltip = not conf.dockToTooltip
		self:Msg(XPERL_REAPER_DOCKING, OnOff(conf.dockToTooltip))
		if (conf.dockToTooltip and conf.locked) then
			conf.locked = false
			self:Msg(XPERL_REAPER_LOCKED, OnOff(conf.locked))
		end
		self:Tip()
		return

	elseif (args[1] == XPERL_REAPER_CMD_DOCKP) then
		local dock = tonumber(args[2])
		if (dock and dock == 1 or dock == 2) then
			conf.dockPoint = dock
			conf.barsLeft = dock == 1
			self:Msg(XPERL_REAPER_DOCKPOINT, getglobal("XPERL_REAPER_DP"..dock))
			self:Tip()
			return
		end

	elseif (args[1] == XPERL_REAPER_CMD_BARS) then
		conf.bars = not conf.bars
		self:Msg(XPERL_REAPER_BARSET, OnOff(conf.bars))
		self:Tip()
		return

	elseif (args[1] == XPERL_REAPER_CMD_ESTIMATE) then
		conf.estimate = not conf.estimate
		self:Msg(XPERL_REAPER_ESTIMATESET, OnOff(conf.estimate))
		self:Tip()
		return

	elseif (args[1] == XPERL_REAPER_CMD_ENABLE) then
		conf.enabled = not conf.enabled
		self:Msg(XPERL_REAPER_ENABLED, OnOff(conf.enabled))
		self:Enable()
		return

	elseif (args[1] == XPERL_REAPER_CMD_LOCK) then
		self:Lock()
		return

	elseif (args[1] == XPERL_REAPER_CMD_CHANNEL) then
		if (args[2]) then
			self:SetChannel(args[2])
		end
		return

	elseif (args[1] == XPERL_REAPER_CMD_HIDE) then
		conf.hide = not conf.hide
		self:Msg(XPERL_REAPER_HIDEENABLED, OnOff(conf.hide))
		return

	elseif (args[1] == XPERL_REAPER_CMD_SCANBACK) then
		conf.scan = not conf.scan
		self:Msg(XPERL_REAPER_SCANENABLED, OnOff(conf.scan))

	end

	if (#args > 0) then
		self:Report(args)
	end
end

SlashCmdList["XPERL_GRIMREAPER"] = function(msg) XPerl_GrimReaper:SlashCmd(msg) end
SLASH_XPERL_GRIMREAPER1 = "/grim"

gr:SetScript("OnEvent", gr.OnEvent)
gr:RegisterEvent("PLAYER_ENTERING_WORLD")
gr:RegisterEvent("VARIABLES_LOADED")
